package org.apache.lucene.search;
/**
 * Copyright 2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import org.apache.lucene.index.IndexReader;

import java.io.IOException;

/**
 * Wraps another SpanFilter's result and caches it.  The purpose is to allow
 * filters to simply filter, and then wrap with this class to add caching.
 */
public class CachingSpanFilter extends SpanFilter {
    private SpanFilter filter;

    /**
     * A transient Filter cache (package private because of test)
     */
    private final CachingWrapperFilter.FilterCache<SpanFilterResult> cache;

    /**
     * New deletions always result in a cache miss, by default
     * ({@link CachingWrapperFilter.DeletesMode#RECACHE}.
     * @param filter Filter to cache results of
     */
    public CachingSpanFilter(SpanFilter filter) {
        this(filter, CachingWrapperFilter.DeletesMode.RECACHE);
    }

    /**
     * @param filter Filter to cache results of
     * @param deletesMode See {@link CachingWrapperFilter.DeletesMode}
     */
    public CachingSpanFilter(SpanFilter filter, CachingWrapperFilter.DeletesMode deletesMode) {
        this.filter = filter;
        if (deletesMode == CachingWrapperFilter.DeletesMode.DYNAMIC) {
            throw new IllegalArgumentException("DeletesMode.DYNAMIC is not supported");
        }
        this.cache = new CachingWrapperFilter.FilterCache<SpanFilterResult>(deletesMode) {
            @Override
            protected SpanFilterResult mergeDeletes(final IndexReader reader, final SpanFilterResult value) {
                throw new IllegalStateException("DeletesMode.DYNAMIC is not supported");
            }
        };
    }

    @Override
    public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
        SpanFilterResult result = getCachedResult(reader);
        return result != null ? result.getDocIdSet() : null;
    }

    // for testing
    int hitCount, missCount;

    private SpanFilterResult getCachedResult(IndexReader reader) throws IOException {

        final Object coreKey = reader.getCoreCacheKey();
        final Object delCoreKey = reader.hasDeletions() ? reader.getDeletesCacheKey() : coreKey;

        SpanFilterResult result = cache.get(reader, coreKey, delCoreKey);
        if (result != null) {
            hitCount++;
            return result;
        }

        missCount++;
        result = filter.bitSpans(reader);

        cache.put(coreKey, delCoreKey, result);
        return result;
    }

    @Override
    public SpanFilterResult bitSpans(IndexReader reader) throws IOException {
        return getCachedResult(reader);
    }

    @Override
    public String toString() {
        return "CachingSpanFilter(" + filter + ")";
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof CachingSpanFilter))
            return false;
        return this.filter.equals(((CachingSpanFilter) o).filter);
    }

    @Override
    public int hashCode() {
        return filter.hashCode() ^ 0x1117BF25;
    }
}
